/*
 *  Copyright 2012 Alexey Andreev.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package ru.justplay.battleship.actors;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * @author Alexey Andreev
 */
public class DefaultGameDispatcher implements GameDispatcher, Serializable {
    private static final long serialVersionUID = 6056724780755966919L;

    private class DefaultPlayer implements Player, Serializable {
        private static final long serialVersionUID = 2639817951547687130L;
        public InitialState initialState;
        public DefaultPlayer enemy;
        public PlayerObserver ownObserver;
        public Ship[][] ships;
        public boolean[][] hits;

        public DefaultPlayer() {
            ships = new Ship[10][];
            hits = new boolean[10][];
            for (int i = 0; i < 10; ++i) {
                ships[i] = new Ship[10];
                hits[i] = new boolean[10];
            }
        }

        @Override
        public void putInitialState(InitialState initialState) {
            if (this.initialState != null) {
                ownObserver.initialStateRejected();
                return;
            }
            if (!acceptInitialState(initialState)) {
                ownObserver.initialStateRejected();
                return;
            }
            this.initialState = initialState;
            for (Ship ship : initialState.getShips()) {
                int row = ship.getRow();
                int column = ship.getColumn();
                int horz = ship.getOrientation().horz;
                int vert = ship.getOrientation().vert;
                ships[row][column] = ship;
                row += vert;
                column += horz;
            }
            ownObserver.initialStateAccepted();
            if (enemy.ownObserver != null) {
                enemy.ownObserver.enemyStarted();
            }
            if (enemy.initialState != null) {
                for (DefaultPlayer player : secretMap.values()) {
                    player.ownObserver.gameStarted();
                }
            }
        }

        private boolean acceptInitialState(InitialState initialState) {
            Map<Integer, Integer> shipClasses = new HashMap<Integer, Integer>();
            shipClasses.put(1, 4);
            shipClasses.put(2, 3);
            shipClasses.put(3, 4);
            shipClasses.put(4, 4);
            boolean[][] occupied = new boolean[10][];
            for (int i = 0; i < 10; ++i) {
                occupied[i] = new boolean[10];
            }
            for (Ship ship : initialState.getShips()) {
                int row = ship.getRow();
                int column = ship.getColumn();
                int horz = 0;
                int vert = 0;
                switch (ship.getOrientation()) {
                    case HORIZONTAL:
                        horz = 1;
                        break;
                    case VERTICAL:
                        horz = 0;
                        break;
                }
                for (int i = 0; i < ship.getLength(); ++i) {
                    if (!validLocation(row, column)) {
                        return false;
                    }
                    if (occupied[row][column]) {
                        return false;
                    }
                    occupied[row][column] = true;
                    int row1 = row + horz;
                    int column1 = row + vert;
                    if (validLocation(row1, column1)) {
                        occupied[row1][column1] = true;
                    }
                    row1 = row - horz;
                    column1 = row - vert;
                    if (validLocation(row1, column1)) {
                        occupied[row1][column1] = true;
                    }
                    row += vert;
                    column += horz;
                }
                Integer count = shipClasses.get(ship.getLength());
                if (count == null || count == 0) {
                    return false;
                }
                shipClasses.put(ship.getLength(), count - 1);
            }
            for (Integer count : shipClasses.values()) {
                if (count > 0) {
                    return false;
                }
            }
            return true;
        }

        private boolean validLocation(int row, int column) {
            return row >= 0 && row < 10 && column >= 0 && column < 10;
        }

        @Override
        public void fire(int row, int column) {
            if (playerToMove != this) {
                ownObserver.fireRejected();
                return;
            }
            if (!validLocation(row, column)) {
                ownObserver.fireRejected();
                return;
            }
            if (enemy.hits[row][column]) {
                ownObserver.fireRejected();
                return;
            }
            enemy.hits[row][column] = true;
            enemy.ownObserver.hit(row, column);
            Ship ship = enemy.ships[row][column];
            if (ship == null) {
                ownObserver.missed();
                playerToMove = enemy;
                playerToMove.ownObserver.gotFire();
                return;
            }
            if (isKilled(ship, hits)) {
                ownObserver.killed(ship);
            } else {
                ownObserver.injured();
            }
        }
    }
    private Map<String, DefaultPlayer> secretMap = new HashMap<String, DefaultPlayer>();
    private DefaultPlayer playerToMove;

    public DefaultGameDispatcher(String firstSecret, String secondSecret) {
        DefaultPlayer firstPlayer = new DefaultPlayer();
        DefaultPlayer secondPlayer = new DefaultPlayer();
        secretMap.put(firstSecret, firstPlayer);
        secretMap.put(secondSecret, secondPlayer);
        firstPlayer.enemy = secondPlayer;
        secondPlayer.enemy = firstPlayer;
        playerToMove = firstPlayer;
    }

    private static boolean isKilled(Ship ship, boolean[][] hits) {
        int horz = ship.getOrientation().horz;
        int vert = ship.getOrientation().vert;
        int row = ship.getRow();
        int column = ship.getColumn();
        for (int i = 0; i < ship.getLength(); ++i) {
            if (!hits[row][column]) {
                return false;
            }
            row += vert;
            column += horz;
        }
        return true;
    }

    @Override
    public void login(PlayerObserver observer, String secret) {
        DefaultPlayer player = secretMap.get(secret);
        if (player == null) {
            observer.loginRejected();
        }
        player.ownObserver = observer;
        GameState state = new GameState();
        state.setInitialState(player.initialState);
        state.setOpponentReady(player.enemy.initialState != null);
        state.setOwnSea(player.hits);
        CellState[][] oppositeSea = new CellState[10][];
        for (int i = 0; i < 10; ++i) {
            oppositeSea[i] = new CellState[10];
            for (int j = 0; j < 10; ++j) {
                oppositeSea[i][j] = player.enemy.hits[i][j] ? CellState.WATER :
                        CellState.UNKNOWN;
            }
        }
        if (player.enemy.initialState != null) {
            for (Ship ship : player.enemy.initialState.getShips()) {
                boolean killed = isKilled(ship, player.enemy.hits);
                int row = ship.getRow();
                int column = ship.getColumn();
                int horz = ship.getOrientation().horz;
                int vert = ship.getOrientation().vert;
                for (int i = 0; i < ship.getLength(); ++i) {
                    if (player.enemy.hits[row][column]) {
                        oppositeSea[row][column] = killed ? CellState.KILLED_SHIP :
                                CellState.INJURED_SHIP;
                    }
                    row += vert;
                    column += horz;
                }
            }
        }
        state.setEnemySea(oppositeSea);
        observer.gameStateChanged(state);
    }
}
